/*
 * @(#)GenericTerrainMap.java     14/08/02
 * 
 * Author             Rick Wilson
 * Copyright (c) 2014 Rick Wilson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of 'Taareekah' nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */



package world;

//~--- non-JDK imports --------------------------------------------------------

import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;

import gameobject.GOManager.GameObject;

import util.BMPFile;
import util.BitmapPixelShader;
import util.Point;

//~--- JDK imports ------------------------------------------------------------

import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

import java.nio.ByteBuffer;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.JMException;

/**
 *
 * @author rick
 */
public class GenericTerrainMap extends GameObject {

    /**
     *
     */
    protected int	size;

    /**
     *
     */
    protected float[]	values;

    /**
     *
     */
    protected boolean	is_loaded;

    /**
     *
     */
    protected transient BitmapPixelShader	shader;

    /**
     *
     */
    protected transient Material	material;

    /**
     *
     */
    public GenericTerrainMap() {
	this.is_loaded	= false;
	this.size	= -1;
	this.values	= null;
    }

    /**
     *
     * @param size
     * @throws Exception
     */
    public GenericTerrainMap(int size) throws Exception {
	this();
	resize(size);
    }

    /**
     *
     * @return
     * @throws Exception
     */
    public BitmapPixelShader getBitmapPixelShader() throws Exception {
	return new GenericBitmapGreyscaleShader(this);
    }

    /**
     *
     * @return
     */
    public Material getMaterial() {
	return material;
    }

    /**
     *
     * @param mat
     */
    public void setMaterial(Material mat) {
	material	= mat;
    }

    /**
     *
     * @param asset_manager
     * @return
     */
    public Material createMaterial(AssetManager asset_manager) {
	if (material != null) {
	    return material;
	}

	material	= new Material(asset_manager, "Common/MatDefs/Misc/Unshaded.j3md");
	material.setTexture("ColorMap", createTexture());

	return material;
    }

    /**
     *
     * @return
     */
    public Texture createTexture() {
	ByteBuffer	rgb_bytes	= ByteBuffer.allocateDirect(size * size * 3);

	if (shader == null) {
	    shader	= new GenericBitmapGreyscaleShader(this);
	}

	for (int i = 0; i < size; i++) {
	    for (int j = 0; j < size; j++) {

		// int base = (i + (size - 1 - j) * size) * 3;
		int	base	= (i + j * size) * 3;
		int[]	rgb	= shader.getColorAtPoint(i, j);

		rgb_bytes.put(base, (byte) rgb[0]);
		rgb_bytes.put(base + 1, (byte) rgb[1]);
		rgb_bytes.put(base + 2, (byte) rgb[2]);
	    }
	}

	Image	new_rgb	= new Image(Image.Format.RGB8, size, size, rgb_bytes);
	Texture	texture	= new Texture2D(new_rgb);

	return texture;
    }

    /**
     *
     * @param shader
     */
    public void setShader(BitmapPixelShader shader) {
	this.shader	= shader;
    }

    /**
     *
     * @param x
     * @param y
     * @return
     */
    public int mapCoordsToBitmapIndex(int x, int y) {
	return (x + (size - 1 - y) * size);
    }

    /**
     *
     * @param p
     * @return
     */
    public boolean isPointWithinRange(Point p) {
	return isPointWithinRange(p.getX(), p.getY());
    }

    /**
     *
     * @param x
     * @param y
     * @return
     */
    public boolean isPointWithinRange(int x, int y) {
	if ((values == null) || (size <= 0)) {
	    return false;
	}

	if ((x < 0) || (y < 0) || (x >= size) || (y >= size)) {
	    return false;
	}

	return true;
    }

    /**
     *
     * @return
     */
    public int getSize() {
	return this.size;
    }

    /**
     *
     * @param p
     * @return
     * @throws Exception
     */
    public float getValueAtPoint(Point p) throws Exception {
	return getValueAtPoint(p.getX(), p.getY());
    }

    /**
     *
     * @param x
     * @param y
     * @return
     * @throws Exception
     */
    public float getValueAtPoint(int x, int y) throws Exception {
	if (!isPointWithinRange(x, y)) {
	    throw new JMException("Invalid point");
	}

	return values[x + y * size];
    }

    /**
     *
     * @param filename
     * @return
     * @throws Exception
     */
    public boolean saveAsRaw(String filename) throws Exception {
	if (null == filename) {
	    throw new Exception("Filename must not be null");
	}

	// open the streams and send the height data to the file.
	try {
	    FileOutputStream	fos	= new FileOutputStream(filename);
	    DataOutputStream	dos	= new DataOutputStream(fos);

	    for (int i = 0; i < size; i++) {
		for (int j = 0; j < size; j++) {
		    dos.writeFloat(getValueAtPoint(i, j));
		}
	    }

	    fos.close();
	    dos.close();
	} catch (FileNotFoundException e) {
	    Logger.getLogger(GenericTerrainMap.class.getName()).log(Level.WARNING, "Error opening file {0}", filename);

	    return false;
	} catch (IOException e) {
	    Logger.getLogger(GenericTerrainMap.class.getName()).log(Level.WARNING, "Error writing to file {0}",
			     filename);

	    return false;
	}

	Logger.getLogger(GenericTerrainMap.class.getName()).log(Level.FINE, "Saved terrain to {0}", filename);

	return true;
    }

    /**
     *
     * @param filename
     * @param shader
     * @return
     * @throws Exception
     */
    public boolean saveAsBitmap(String filename, BitmapPixelShader shader) throws Exception {
	return BMPFile.saveBMP(filename, size, size, shader);
    }

    /**
     *
     * @param filename
     * @return
     * @throws Exception
     */
    public boolean saveAsBitmap(String filename) throws Exception {
	if (shader == null) {
	    shader	= new GenericBitmapGreyscaleShader(this);
	}

	return BMPFile.saveBMP(filename, size, size, shader);
    }

    /**
     *
     * @param filename
     * @return
     * @throws Exception
     */
    public boolean saveAsHumanReadable(String filename) throws Exception {
	if (null == filename) {
	    throw new Exception("Filename must not be null");
	}

	if (values == null) {
	    return false;
	}

	// open the streams and send the height data to the file.
	try {
	    FileWriter	o	= new FileWriter(filename);

	    for (int i = 0; i < size; i++) {
		for (int j = 0; j < size; j++) {
		    String	out	= "(" + i + ", " + j + ") = " + getValueAtPoint(i, j) + ";\n";

		    o.write(out);
		}
	    }

	    o.close();
	} catch (FileNotFoundException e) {
	    Logger.getLogger(GenericTerrainMap.class.getName()).log(Level.SEVERE, "Error opening file {0}", filename);

	    return false;
	} catch (IOException e) {
	    Logger.getLogger(GenericTerrainMap.class.getName()).log(Level.SEVERE, "Error writing to file {0}",
			     filename);

	    return false;
	}

	return true;
    }

    /**
     *
     * @return
     */
    public boolean isLoaded() {
	if ((is_loaded == false) || (size <= 0) || (values == null)) {
	    return false;
	}

	return true;
    }

    /**
     *
     * @param is_loaded
     */
    public void setIsLoaded(boolean is_loaded) {
	this.is_loaded	= is_loaded;
    }

    /**
     *
     * @param p
     * @param value
     * @return
     * @throws Exception
     */
    public boolean setValueAtPoint(Point p, float value) throws Exception {
	return setValueAtPoint(p.getX(), p.getY(), value);
    }

    /**
     *
     * @param x
     * @param y
     * @param value
     * @return
     * @throws Exception
     */
    public boolean setValueAtPoint(int x, int y, float value) throws Exception {
	if (!isPointWithinRange(x, y)) {
	    throw new JMException("Invalid point");
	}

	try {
	    values[x + y * size]	= value;
	} catch (Exception e) {
	    Logger.getLogger(GenericTerrainMap.class.getName()).log(Level.SEVERE, "({0}, {1}), size={2}, value={3}",
			     new Object[] { x,
					    y, size, value });

	    throw e;
	}

	return true;
    }

    /**
     *
     * @return
     * @throws Exception
     */
    public float[] getAllValues() throws Exception {
	return values;
    }

    /**
     *
     * @param size
     * @return
     * @throws Exception
     */
    public boolean resize(int size) throws Exception {
	if (size < 0) {
	    throw new JMException("Invalid size");
	}

	this.size	= size;
	values		= null;
	values		= new float[size * size];
	is_loaded	= false;
	shader		= null;

	return true;
    }

    /**
     *
     */
    public class GenericBitmapGreyscaleShader extends BitmapPixelShader {

	/** Field description */
	private GenericTerrainMap	map;

	/**
	 *
	 * @param map
	 */
	public GenericBitmapGreyscaleShader(GenericTerrainMap map) {
	    this.map	= map;
	}

	/**
	 *
	 * @param x
	 * @param y
	 * @return
	 */
	public int[] getColorAtPoint(int x, int y) {
	    try {
		int	temp	= Math.round(map.getValueAtPoint(x, y));

		if (temp > 255) {
		    return new int[] { 255, 255, 255 };
		} else if (temp < 0) {
		    return new int[] { 0, 0, 0 };
		}

		return new int[] { temp, temp, temp };
	    } catch (Exception e) {}

	    return new int[] { 0, 0, 0 };
	}
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
